////
/// @group core.utils
////

@use "sass:list";
@use "sass:map";
@use "sass:meta";
@use "sass:string";

/// Set this to `true` if you do not need to support RTL languages
/// @type Boolean
$disable-rtl: false !default;

/// Set this to `true` if you want to disable build-time validation for
/// overriding react-md values. No idea why you would want this though.
/// @type Boolean
$disable-validation: false !default;

/// Set this to `true` to remove the `:has` selectors in the code if you need to
/// support older browsers even though I don't really support older browsers.
/// @type Boolean
$disable-has-selectors: false !default;

/// Set this to `true` if focus styles should be applied with
/// `.rmd-keyboard-mode :focus` instead of `:focus-visible`.
/// @type Boolean
$disable-focus-visible: false !default;

/// The default z-index to use for temporary elements
/// @type Boolean
$temporary-element-z-index: 30 !default;

/// @access private
/// @type List
$_swappable-positions: top right bottom left;
/// @access private
/// @type List
$_swappable-position-prefixes: border margin padding;
/// @access private
/// @type List
$_swappable-properties: text-align;

/// This function can be used to offset `position: fixed` or `position: absolute`
/// elements by the current scrollbar size. The variable will only be set while
/// the `useScrollLock` hook has been triggered.
///
/// @example scss - Example Usage SCSS
///   .selector {
///     margin-right: scrollbar-size();
///   }
///
/// @returns {String} a `var` string
@function scrollbar-size() {
  @return var(--rmd-scrollbar-size, 0px);
}

/// Replace `$search` with `$replace` in `$string`
///
/// @author Hugo Giraudel
/// @access private
/// @param {String} string - Initial string
/// @param {String} search - Substring to replace
/// @param {String} replace - New value
/// @returns {String} Updated string
/// @link https://css-tricks.com/snippets/sass/str-replace-function Source from CSS Tricks
@function str-replace($string, $search, $replace) {
  $index: string.index($string, $search);

  @if $index {
    @return string.slice($string, 1, $index - 1) + $replace +
      str-replace(
        string.slice($string, $index + string.length($search)),
        $search,
        $replace
      );
  }

  @return $string;
}

/// Used to negate a number or a variable that might be a `var` statement.
///
/// @param {String} css-variable - The `var()` or number to negate.
/// @returns {String} the calc string
@function negate-var($css-variable) {
  @return calc(-1 * #{$css-variable});
}

/// Internal validation function to ensure the user overrides variables with
/// accepted values.
///
/// @since 6.4.0 Added support for strings so that single item lists can be used.
/// @param {List|Map|String} options - The list or map of available options.
/// This can also be a single item list.
/// @param {String|Number} key-or-value - Either the map key or list value to
/// check if exists
/// @param {String} error-message - The additional error message to display
/// @returns {any} the value from the list or map
@function validate($options, $key-or-value, $error-message) {
  $options: if(
    meta.type-of($options) == string,
    list.append((), $options),
    $options
  );

  $type: meta.type-of($options);
  $is-map: $type == map;
  $is-list: $type == list;

  @if $disable-validation {
    @return if($is-list, $key-or-value, map.get($options, $key-or-value));
  }

  @if not $is-map and not $is-list {
    @error "Unable to validate anything except for lists and maps at this time. Received: '#{$options}'.";
  }

  $choices: if($is-map, map.keys($options), $options);
  @if not list.index($choices, $key-or-value) {
    @error "Invalid #{$error-message}: '#{$key-or-value}'. Choose one of: #{$choices}";
  }

  @return if($is-list, $key-or-value, map.get($options, $key-or-value));
}

/// Used to get a custom property variable name.
///
/// @access private
/// @param {List} variables - The list of available variables
/// @param {String} name - The variable name
/// @param {String} group - The variable group. i.e. button, app-bar
/// @returns {String} the name to be used in a `var()`
@function get-var-name($variables, $name, $group) {
  @if $group == icon {
    @if $name == symbol-grade {
      @return --rmd-symbol-grad;
    }
    @if $name == symbol-weight {
      @return --rmd-symbol-wght;
    }
    @if $name == symbol-optical-size {
      @return --rmd-symbol-opsz;
    }
  }
  @if $group == name and $name == fab-offset {
    @return --rmd-fab-offset;
  }

  @if $group == interaction and $name == background-color {
    @return --rmd-interaction-#{$name};
  }

  @if $group == list {
    $updated: str-replace(
      str-replace($name, "horizontal-padding", "padding-h"),
      "vertical-padding",
      "padding-v"
    );
    @if $updated != $name {
      @return --rmd-list-#{$name};
    }
  }

  @if $group == snackbar and $name != offset {
    @return --rmd-toast-#{$name};
  }

  $prefix: "";
  @if not list.index((theme, interaction, transition, typography), $group) {
    $prefix: $group + "-";
  }

  @return "--rmd-" + $prefix + validate($variables, $name, $group + " var");
}

/// Converts a `property: value` map into styles. Also supports custom selectors.
///
/// Note: If the styles include complex selectors like `.parent &`, it will need
/// to be the last mixin in the current block or wrap other styles in `& {}`
///
/// @example scss - Simple Example
///   $styles: (
///     background-color: red,
///     color: blue,
///     padding: 0.25rem,
///   );
///   .container {
///     @include map-to-styles($styles);
///   }
///
/// @example scss - Selectors
///   $styles: (
///     background-color: red,
///
///     '&:hover': (
///       background-color: orange,
///       color: red,
///     ),
///   );
///
///   .container {
///     @include map-to-styles($styles);
///   }
///
/// @param {Map} map - the map of styles to generate
@mixin map-to-styles($map) {
  @each $property, $value in $map {
    @if meta.type-of($value) == map {
      #{$property} {
        @include map-to-styles($value);
      }
    } @else {
      #{$property}: $value;
    }
  }
}

/// Used to convert a map into a css animation
///
/// @example scss
///   $animation: (
///     0%: (
///       opacity: 0.06,
///     ),
///     60%: (
///       opacity: 0.1,
///     ),
///     80%: (
///       opacity: 0.08,
///     ),
///     100%: (
///       opacity: 0.06,
///     ),
///   );
///
///   @keyframes my-animation {
///     @include map-to-animation($animation);
///   }
///
/// @param {Map} map - the map of animation styles to generate
@mixin map-to-animation($map) {
  @each $percentage, $styles in $map {
    #{$percentage} {
      @include map-to-styles($styles);
    }
  }
}

/// Generates a pseudo element with most used styles.
///
/// @example scss
///   .container {
///     @include pseudo-element;
///
///     position: relative;
///   }
///
/// @param {Number} z-index [0] - The z-index for the pseudo element.
/// @param {Boolean} fixed [false] - Set to `true` to change from `position:
/// absolute` to `position: fixed`
/// @param {Number} inset [0] - Used to apply any inset styles from the parent
/// element.
/// @param {String} border-radius [inherit] - You normally want to inherit the
/// `border-radius`, but it can be configured with this param if needed.
@mixin pseudo-element(
  $z-index: 0,
  $fixed: false,
  $inset: 0,
  $border-radius: inherit
) {
  border-radius: $border-radius;
  content: "";
  inset: $inset;
  pointer-events: none;
  position: if($fixed, fixed, absolute);
  z-index: $z-index;
}

/// @access private
@mixin _sr-only-focusable {
  &:active,
  &:focus {
    clip-path: none;
    height: auto;
    margin: auto;
    overflow: visible;
    white-space: normal;
    width: auto;
  }
}

/// Generates styles to create a screen-reader only visible element but can also
/// allow the element to be visible to everyone while focused.
///
/// You'll normally just want to use the `SrOnly` component or `cssUtils` instead
/// of this mixin.
///
/// @param {Boolean} focusable [false] - Set to `true` to update the element so
/// it will become visible to everyone while focused
/// @param {String} focus-selector ["&--focusable"] - A selector to show that
/// the element has been focused
@mixin sr-only($focusable: false, $focus-selector: "&--focusable") {
  border: 0;
  clip-path: inset(50%);
  height: 1px;
  margin: -1px;
  overflow: hidden;
  padding: 0;
  position: absolute;
  white-space: nowrap;
  width: 1px;

  @if $focusable {
    @if not $focus-selector {
      @include _sr-only-focusable;
    } @else {
      #{$focus-selector} {
        @include _sr-only-focusable;
      }
    }
  }
}

/// Used to hide the scrollbar for an element while still remaining scrollable.
///
/// NOTE: This is generally not recommended for accessibility since most users do
/// not know how to scroll without a visible scrollbar
@mixin hide-scrollbar {
  scrollbar-width: none;

  &::-webkit-scrollbar {
    height: 0;
    width: 0;
  }
}

/// Mostly an internal mixin for generating styles that might be created from a CSS
/// module. This really just wraps the provided selector in `:global` and
/// wrapping the rest of the content in `:local`.
///
/// @param {String} selector - The selector that should not be affected by CSS
/// module behavior
/// @param {Boolean} css-modules [false] - Set to `true` when used within a CSS
/// Module
/// @param {Boolean} parent-selector [true] - Updates the selector to end with
/// ` &` so it inherits the current scope
@mixin optional-css-modules(
  $selector,
  $css-modules: false,
  $parent-selector: true
) {
  $selector: if($css-modules, ":global #{$selector} :local", $selector);
  $selector: if($parent-selector, "#{$selector} &", $selector);

  #{$selector} {
    @content;
  }
}

/// Used to apply styles when using rtl behavior
///
/// Note: This will need to be included at the end of a block with the new Sass
/// rules or prefix styles with `& {}`
///
/// @example scss
///   .selector {
///     margin-right: 1rem;
///
///     // swap the margin in RTL
///     @include rtl {
///       margin-left: 1rem;
///       margin-right: auto;
///     }
///   }
@mixin rtl {
  @if not $disable-rtl {
    [dir="rtl"] & {
      @content;
    }
  }
}

/// This isn't really used anymore after the latest Sass changes since CSS will
/// natively support nesting.
///
/// @access private
/// @param {String} property - The property to swap
/// @returns {String} the swapped property
@function swap-position($property) {
  $prefix: "";
  $position: $property;
  @each $valid-prefix in $_swappable-position-prefixes {
    @if string.index($property, "#{$valid-prefix}-") {
      $prefix: "#{$valid-prefix}-";
      $position: str-replace($property, $prefix, "");
    }
  }

  @if not list.index($_swappable-positions, $position) {
    @error "Invalid swappable position: '#{$position}'. Choose one of #{$_swappable-positions}";
  }

  $next-position: bottom;
  @if $position == left {
    $next-position: right;
  } @else if $position == right {
    $next-position: left;
  } @else if $position == bottom {
    $next-position: top;
  }

  @return #{$prefix}#{$next-position};
}

/// Used to apply styles only while the user is using a keyboard. This relies on
/// the `UserInteractionModeProvider` to work correctly.
///
/// Note: This will need to be included at the end of a block with the new Sass
/// rules or prefix styles with `& {}`
///
/// @param {Boolean} css-modules [false] - Set to `true` when used within a CSS
/// Module
/// @param {Boolean} parent-selector [true] - Updates the selector to end with
/// ` &` so it inherits the current scope
@mixin keyboard-only($css-modules: false, $parent-selector: true) {
  @include optional-css-modules(
    ".rmd-keyboard-mode",
    $css-modules,
    $parent-selector
  ) {
    @content;
  }
}

/// Used to apply styles only while the user is using a mouse. This relies on
/// the `UserInteractionModeProvider` to work correctly.
///
/// Note: This will need to be included at the end of a block with the new Sass
/// rules or prefix styles with `& {}`
///
/// @param {Boolean} css-modules [false] - Set to `true` when used within a CSS
/// Module
/// @param {Boolean} parent-selector [true] - Updates the selector to end with
/// ` &` so it inherits the current scope
@mixin mouse-only($css-modules: false, $parent-selector: true) {
  @include optional-css-modules(
    ".rmd-mouse-mode",
    $css-modules,
    $parent-selector
  ) {
    @content;
  }
}

/// Used to apply styles only while the user is using a touch device. This relies
/// on the `UserInteractionModeProvider` to work correctly.
///
/// Note: This will need to be included at the end of a block with the new Sass
/// rules or prefix styles with `& {}`
///
/// @param {Boolean} css-modules [false] - Set to `true` when used within a CSS
/// Module
/// @param {Boolean} parent-selector [true] - Updates the selector to end with
/// ` &` so it inherits the current scope
@mixin touch-only($css-modules: false, $parent-selector: true) {
  @include optional-css-modules(
    ".rmd-touch-mode",
    $css-modules,
    $parent-selector
  ) {
    @content;
  }
}

/// A helper mixin to apply hover styles only when the user is using a mouse. If
/// you just use `:hover`, the styles will also be applied after touching an
/// element on touch screens until another element is focused.
///
/// @example scss
///   .selector {
///
///     @include mouse-hover {
///       background-color: red;
///     }
///   }
///
/// @param {Boolean} wrap-in-hover [true] - Set this to `false` if the content
/// should not automatically be wrapped in `&:hover`
@mixin mouse-hover($wrap-in-hover: true) {
  @media (hover: hover) and (pointer: fine) {
    @if $wrap-in-hover {
      &:hover {
        @content;
      }
    } @else {
      @content;
    }
  }
}

/// An internal helper to dynamically wrap the CSS in a `@layer`.
///
/// @param {String} name - The layer name to use
/// @param {Boolean} disabled - Set to `true` if the content should not be
/// wrapped in the layer.
@mixin optional-layer($name, $disabled) {
  @if $disabled {
    @content;
  } @else {
    @layer #{$name} {
      @content;
    }
  }
}
